import QuantLib as ql
import pandas as pdAsset swaps
today = ql.Date(20, ql.May, 2021)
ql.Settings.instance().evaluationDate = todayThe AssetSwap class builds a swap that exchanges the coupons from a given bond for floating-rate coupons.
Let’s take a fixed-rate bond as an example:
schedule = ql.Schedule(
ql.Date(8, ql.February, 2020),
ql.Date(8, ql.February, 2025),
ql.Period(6, ql.Months),
ql.TARGET(),
ql.Following,
ql.Following,
ql.DateGeneration.Backward,
False,
)
settlementDays = 3
faceAmount = 100
coupons = [0.03]
paymentDayCounter = ql.Thirty360(ql.Thirty360.BondBasis)
bond = ql.FixedRateBond(
settlementDays, faceAmount, schedule, coupons, paymentDayCounter
)Besides the bond, the AssetSwap constructor takes the floating-rate index used to fix the exchanged coupons…
forecast_curve = ql.RelinkableYieldTermStructureHandle(
ql.FlatForward(today, 0.01, ql.Actual360())
)
index = ql.Euribor6M(forecast_curve)…and other parameters: a spread over the floating rate, the bond price, the schedule for the floating-rate coupons (which is optional: if we pass an empty one, the swap will use the same as the bond), the day-count convention for the floating-rate coupons, and a couple of flags specifying the kind of swap we’re creating.
spread = 0.0050
bond_price = 103.0
pay_fixed = True
par_asset_swap = False
swap = ql.AssetSwap(
pay_fixed,
bond,
bond_price,
index,
spread,
ql.Schedule(),
index.dayCounter(),
par_asset_swap,
)When par_asset_swap = False, the swap creates floating-rate coupons paid on a notional equal to the bond price. As for bonds, it’s possible to extract the coupons and retrieve information on each one:
def print_coupon_info(cashflows):
data = []
for cf in cashflows:
c = ql.as_coupon(cf)
if c is not None:
data.append((c.date(), c.rate(), c.nominal(), c.amount()))
else:
data.append((cf.date(), None, None, cf.amount()))
return pd.DataFrame(
data, columns=["date", "rate", "notional", "amount"]
).style.format(
{"amount": "{:.2f}", "notional": "{:.2f}", "rate": "{:.2%}"}
)print_coupon_info(bond.cashflows())| date | rate | notional | amount | |
|---|---|---|---|---|
| 0 | August 10th, 2020 | 3.00% | 100.00 | 1.50 |
| 1 | February 8th, 2021 | 3.00% | 100.00 | 1.48 |
| 2 | August 9th, 2021 | 3.00% | 100.00 | 1.51 |
| 3 | February 8th, 2022 | 3.00% | 100.00 | 1.49 |
| 4 | August 8th, 2022 | 3.00% | 100.00 | 1.50 |
| 5 | February 8th, 2023 | 3.00% | 100.00 | 1.50 |
| 6 | August 8th, 2023 | 3.00% | 100.00 | 1.50 |
| 7 | February 8th, 2024 | 3.00% | 100.00 | 1.50 |
| 8 | August 8th, 2024 | 3.00% | 100.00 | 1.50 |
| 9 | February 10th, 2025 | 3.00% | 100.00 | 1.52 |
| 10 | February 10th, 2025 | nan% | nan | 100.00 |
print_coupon_info(swap.leg(0))| date | rate | notional | amount | |
|---|---|---|---|---|
| 0 | August 9th, 2021 | 3.00% | 100.00 | 1.51 |
| 1 | February 8th, 2022 | 3.00% | 100.00 | 1.49 |
| 2 | August 8th, 2022 | 3.00% | 100.00 | 1.50 |
| 3 | February 8th, 2023 | 3.00% | 100.00 | 1.50 |
| 4 | August 8th, 2023 | 3.00% | 100.00 | 1.50 |
| 5 | February 8th, 2024 | 3.00% | 100.00 | 1.50 |
| 6 | August 8th, 2024 | 3.00% | 100.00 | 1.50 |
| 7 | February 10th, 2025 | 3.00% | 100.00 | 1.52 |
| 8 | February 10th, 2025 | nan% | nan | 100.00 |
print_coupon_info(swap.leg(1))| date | rate | notional | amount | |
|---|---|---|---|---|
| 0 | August 10th, 2021 | 1.50% | 103.89 | 0.33 |
| 1 | February 10th, 2022 | 1.50% | 103.89 | 0.80 |
| 2 | August 10th, 2022 | 1.50% | 103.89 | 0.78 |
| 3 | February 10th, 2023 | 1.50% | 103.89 | 0.80 |
| 4 | August 10th, 2023 | 1.50% | 103.89 | 0.78 |
| 5 | February 12th, 2024 | 1.50% | 103.89 | 0.81 |
| 6 | August 12th, 2024 | 1.50% | 103.89 | 0.79 |
| 7 | February 10th, 2025 | 1.50% | 103.89 | 0.79 |
| 8 | February 10th, 2025 | nan% | nan | 103.89 |
When par_asset_swap = True, the floating-rate coupons are paid on a notional equal to 100 and the swap includes an upfront payment:
par_asset_swap = True
swap = ql.AssetSwap(
pay_fixed,
bond,
bond_price,
index,
spread,
ql.Schedule(),
index.dayCounter(),
par_asset_swap,
)print_coupon_info(swap.leg(0))| date | rate | notional | amount | |
|---|---|---|---|---|
| 0 | August 9th, 2021 | 3.00% | 100.00 | 1.51 |
| 1 | February 8th, 2022 | 3.00% | 100.00 | 1.49 |
| 2 | August 8th, 2022 | 3.00% | 100.00 | 1.50 |
| 3 | February 8th, 2023 | 3.00% | 100.00 | 1.50 |
| 4 | August 8th, 2023 | 3.00% | 100.00 | 1.50 |
| 5 | February 8th, 2024 | 3.00% | 100.00 | 1.50 |
| 6 | August 8th, 2024 | 3.00% | 100.00 | 1.50 |
| 7 | February 10th, 2025 | 3.00% | 100.00 | 1.52 |
| 8 | February 10th, 2025 | nan% | nan | 100.00 |
print_coupon_info(swap.leg(1))| date | rate | notional | amount | |
|---|---|---|---|---|
| 0 | May 25th, 2021 | nan% | nan | 3.89 |
| 1 | August 10th, 2021 | 1.50% | 100.00 | 0.32 |
| 2 | February 10th, 2022 | 1.50% | 100.00 | 0.77 |
| 3 | August 10th, 2022 | 1.50% | 100.00 | 0.76 |
| 4 | February 10th, 2023 | 1.50% | 100.00 | 0.77 |
| 5 | August 10th, 2023 | 1.50% | 100.00 | 0.76 |
| 6 | February 12th, 2024 | 1.50% | 100.00 | 0.78 |
| 7 | August 12th, 2024 | 1.50% | 100.00 | 0.76 |
| 8 | February 10th, 2025 | 1.50% | 100.00 | 0.76 |
| 9 | February 10th, 2025 | nan% | nan | 100.00 |
In both cases, once we give it an discounting engine, the swap can return more information.
discount_curve = ql.YieldTermStructureHandle(
ql.FlatForward(today, 0.02, ql.Actual360())
)
swap.setPricingEngine(ql.DiscountingSwapEngine(discount_curve))The NPV and legNPV methods return the value of the swap or of either leg. In this case we’re paying the bond coupons, therefore the corresponding leg has a negative value.
print(swap.NPV())
print(swap.legNPV(0))
print(swap.legNPV(1))-2.230481904331157
-104.26057030905751
102.03008840472636
It’s also possible to retrieve the spread over the floating index that would make the swap fair:
fair_spread = swap.fairSpread()
print(fair_spread)0.01117507740466585
We can test it by re-building the swap with this spread and asking for the NPV again:
swap = ql.AssetSwap(
pay_fixed,
bond,
bond_price,
index,
fair_spread,
ql.Schedule(),
index.dayCounter(),
par_asset_swap,
)
swap.setPricingEngine(ql.DiscountingSwapEngine(discount_curve))print(swap.NPV())
print(swap.legNPV(0))
print(swap.legNPV(1))0.0
-104.26057030905751
104.26057030905751
print_coupon_info(swap.leg(0))| date | rate | notional | amount | |
|---|---|---|---|---|
| 0 | August 9th, 2021 | 3.00% | 100.00 | 1.51 |
| 1 | February 8th, 2022 | 3.00% | 100.00 | 1.49 |
| 2 | August 8th, 2022 | 3.00% | 100.00 | 1.50 |
| 3 | February 8th, 2023 | 3.00% | 100.00 | 1.50 |
| 4 | August 8th, 2023 | 3.00% | 100.00 | 1.50 |
| 5 | February 8th, 2024 | 3.00% | 100.00 | 1.50 |
| 6 | August 8th, 2024 | 3.00% | 100.00 | 1.50 |
| 7 | February 10th, 2025 | 3.00% | 100.00 | 1.52 |
| 8 | February 10th, 2025 | nan% | nan | 100.00 |
print_coupon_info(swap.leg(1))| date | rate | notional | amount | |
|---|---|---|---|---|
| 0 | May 25th, 2021 | nan% | nan | 3.89 |
| 1 | August 10th, 2021 | 2.12% | 100.00 | 0.45 |
| 2 | February 10th, 2022 | 2.12% | 100.00 | 1.08 |
| 3 | August 10th, 2022 | 2.12% | 100.00 | 1.07 |
| 4 | February 10th, 2023 | 2.12% | 100.00 | 1.08 |
| 5 | August 10th, 2023 | 2.12% | 100.00 | 1.07 |
| 6 | February 12th, 2024 | 2.12% | 100.00 | 1.10 |
| 7 | August 12th, 2024 | 2.12% | 100.00 | 1.07 |
| 8 | February 10th, 2025 | 2.12% | 100.00 | 1.07 |
| 9 | February 10th, 2025 | nan% | nan | 100.00 |
Asset swaps can be built based on other kinds of bonds besides fixed-rate ones; the resulting instances work the same way.